<?
package("jpunit.runner");

import("jpunit.framework.Assert");
import("jpunit.framework.AssertionFailedError");
import("jpunit.framework.ComparisonFailure");
import("jpunit.framework.Protectable");
import("jpunit.framework.Test");
import("jpunit.framework.TestCase");
import("jpunit.framework.TestFailure");
import("jpunit.framework.TestListener");
import("jpunit.framework.TestResult");
import("jpunit.framework.TestSuite");
import("jpunit.framework.TestListener");
import("japha.lang.reflect.*");
import("japha.text.NumberFormat");
import("japha.io.*");
import("japha.util.*");

/**
 * $Id: BaseTestRunner.php,v 1.4 2005/06/16 16:06:48 japha Exp $
 *
 * Base class for all test runners.
 * This class was born live on stage in Sardinia during XP2000.
 *
 * @author <a href="mailto:gantt@cs.montana.edu">Ryan Gantt</a>
 * @version $Revision: 1.4 $
 */
abstract class BaseTestRunner implements TestListener
{
	const SUITE_METHODNAME= "suite";

	private static $fPreferences = null;
	public static $fgMaxMessageLength= 500;
	public static $fgFilterStack= true;
	public $fLoading= true;

    /*
    * Implementation of TestListener
    */
	public function startTest( Test $test ) //synchronized
	{
		$this->testStarted( $test->toString() );
	}

	protected static function setPreferences( Properties $preferences ) 
	{
		$this->fPreferences = $preferences;
	}

	protected static function getPreferences() 
	{
		if ( self::$fPreferences == null) 
		{
			self::$fPreferences = new Properties();
	 		self::$fPreferences->put("loading", "true");
 			self::$fPreferences->put("filterstack", "true");
  			self::readPreferences();
		}
		return self::$fPreferences;
	}

	public static function savePreferences() //throws IOException {
	{
		$fos = new FileOutputStream( $this->getPreferencesFile() );
		try 
		{
			$this->getPreferences()->store( $fos, "" );
		}
		catch (Exception $e )
		{	
		}
		$fos->close();
	}

	public function setPreference( $key, $value) 
	{
		$this->getPreferences()->setProperty( $key, $value );
	}

	public function endTest( Test $test )  //synchronizes
	{
		$this->testEnded( $test->toString() );
	}

	public function addError( Test $test, Throwable $t ) //synchronized
	{
		$this->testFailed( TestRunListener::STATUS_ERROR, $test, $t );
	}

	public function addFailure( Test $test, AssertionFailedError $t )  //syncrhonized
	{
		$this->testFailed( TestRunListener::STATUS_FAILURE, $test, $t );
	}

	// TestRunListener implementation

	public abstract function testStarted( $testName );

	public abstract function testEnded( $testName );

	public abstract function testFailed( $status, Test $test, Throwable $t );

	/**
	 * Returns the Test corresponding to the given suite. This is
	 * a template method, subclasses override runFailed(), clearStatus().
	 */
	public function getTest( $suiteClassName ) 
	{
		if (strlen($suiteClassName) <= 0) 
		{
			$this->clearStatus();
			return null;
		}
		$testClass = null; //_Class
		try 
		{
			$testClass = $this->loadSuiteClass( $suiteClassName );
		} 
		catch (ClassNotFoundException $e) 
		{
			$clazz= $e->getMessage();
			if ($clazz == null)
				$clazz = $suiteClassName;
			$this->runFailed("Class not found \"" . $clazz . "\"");
			return null;
		} 
		catch(Exception $e) 
		{
			$this->runFailed("Error: " . $e->toString());
			return null;
		}
		$suiteMethod = null; //Method
		try 
		{
			$suiteMethod = $testClass->getMethod(SUITE_METHODNAME, array( new _Class ));
	 	} 
	 	catch(Exception $e) 
	 	{
	 		// try to extract a test suite automatically
			$this->clearStatus();
			return new TestSuite($testClass);
		}
		if (! Modifier::isStatic($suiteMethod->getModifiers())) {
			$this->runFailed("Suite() method must be static");
			return null;
		}
		$test = null; //Test
		try 
		{
			$test = $suiteMethod->invoke(null, array( new _Class )); // static method
			if ($test == null)
				return $test;
		}
		catch (InvocationTargetException $e) 
		{
			$this->runFailed("Failed to invoke suite():" . $e->getTargetException()->toString());
			return null;
		}
		catch (IllegalAccessException $e) 
		{
			$this->runFailed("Failed to invoke suite():" . $e->toString());
			return null;
		}

		$this->clearStatus();
		return $test;
	}

	/**
	 * Returns the formatted string of the elapsed time.
	 */
	public function elapsedTimeAsString( $runTime ) 
	{
		return NumberFormat::getInstance()->format((double)$runTime/1000);
	}

	/**
	 * Processes the command line arguments and
	 * returns the name of the suite class to run or null
	 */
	protected function processArguments( $args = array() ) 
	{
		$suiteName = null;
		for ( $i = 0; $i < count($args); $i++ ) 
		{
			if ($args[$i] == "-noloading") 
			{
				$this->setLoading( false );
			} 
			else if ($args[$i] == "-nofilterstack") 
			{
				$this->fgFilterStack= false;
			} 
			else if ($args[$i] == "-c") 
			{
				if (count($args) > $i + 1)
				{
					$suiteName = $this->extractClassName( $args[$i + 1] );
				}
				else
				{
					print("Missing Test class name");
				}
				$i++;
			} 
			else 
			{
				$suiteName = $args[$i];
			}
		}
		return $suiteName;
	}

	/**
	 * Sets the loading behaviour of the test runner
	 */
	public function setLoading( $enable ) 
	{
		$this->fLoading = enable;
	}
	/**
	 * Extract the class name from a String in VA/Java style
	 */
	public function extractClassName( $className ) 
	{
		if($className->startsWith("Default package for"))
		{
			return $className->substring($className->lastIndexOf(".") + 1);
		}
		return $className;
	}

	/**
	 * Truncates a String to the maximum length.
	 */
	public static function truncate( $s ) 
	{
		if ($this->fgMaxMessageLength != -1 && strlen($s) > $this->fgMaxMessageLength)
			$s = $s->substring(0, $this->fgMaxMessageLength)."...";
		return $s;
	}

	/**
	 * Override to define how to handle a failed loading of
	 * a test suite.
	 */
	protected abstract function runFailed( $message );

	/**
	 * Returns the loaded Class for a suite name.
	 */
	protected function loadSuiteClass( $suiteClassName ) // throws ClassNotFoundException 
	{
		return $this->getLoader()->load( $suiteClassName );
	}

	/**
	 * Clears the status message.
	 */
	protected function clearStatus()  // Belongs in the GUI TestRunner class
	{
	}

	/**
	 * Returns the loader to be used.
	 */
	public function getLoader() {
		if ( $this->useReloadingTestSuiteLoader() )
			return new ReloadingTestSuiteLoader();
		return new StandardTestSuiteLoader();
	}

	protected function useReloadingTestSuiteLoader() 
	{
		return $this->getPreference("loading")->equals("true") && !$this->inVAJava() && $this->fLoading;
	}

	private static function getPreferencesFile() 
	{
	 	$home = System::getProperty("user.home");
 		return new File( $home, "junit.properties" );
 	}

 	private static function readPreferences() 
 	{
 	    // This doesn't work
 		/*$is = null; //InputStream
 		try 
 		{
 			$is = new FileInputStream( self::getPreferencesFile() );
 			self::setPreferences( new Properties( self::getPreferences() ) );
			self::getPreferences()->load( $is );
		} 
		catch ( IOException $e ) 
		{
			try 
			{
				if ( $is != null )
					$is->close();
			} 
			catch  (IOException $e1 ){}
		}*/
 	}

 	/*public static function getPreference( $key ) 
 	{
 		return $this->getPreferences()->getProperty( $key );
 	}*/

 	public static function getPreference( $key, $dflt = null) 
 	{
 		if( $dflt == null )
 		{
 			return self::getPreferences()->getProperty( $key );
 		}
 			
 		$value = self::getPreference( $key );
 		$intValue = $dflt;
 		if ($value == null)
 			return $intValue;
 		try 
 		{
 			$intValue = Integer::parseInt( $value );
 	 	}
 	 	catch (NumberFormatException $ne) 
 	 	{
 		}
 		return $intValue;
 	}

 	public static function inVAJava() 
 	{
		try 
		{
			_Class::forName("com.ibm.uvm.tools.DebugSupport");
		}
		catch (Exception $e) 
		{
			return false;
		}
		return true;
	}

	/**
	 * Returns a filtered stack trace
	 */
	/*public static function getFilteredTrace( Throwable $t ) 
	{
		$stringWriter = new StringWriter();
		$writer = new PrintWriter( $stringWriter );
		$t->printStackTrace( $writer );
		$buffer = $stringWriter->getBuffer(); //StringBuffer
		$trace = $buffer->toString();
		return BaseTestRunner::getFilteredTrace( $trace );
	}*/

	/**
	 * Filters stack frames from internal JUnit classes
	 */
	public static function getFilteredTrace( $stack ) 
	{
		if ( self::showStackRaw() )
		{
			return $stack;
		}
		$sw = new StringWriter();
		//$pw = new PrintWriter( $sw );
		$pw = new PrintStream( $sw );
		$sr = new StringReader( $stack );
		$br = new BufferedReader( $sr );
		$line = "";
		try 
		{
			while ( ( $line = $br->readLine() ) != false ) 
			{
				if ( !self::filterLine( $line ) )
				{
					$pw->println( $line );
				}
			}
		} 
		catch ( Exception $IOException )
		{
			return $stack; // return the stack unfiltered
		}
		$sb = $sw->getBuffer();
		return $sb->toString();
	}

	protected static function showStackRaw() 
	{
		return ( ( !self::getPreference("filterstack") == true ) || ( self::$fgFilterStack == false ) );
	}

	static function filterLine( $line ) 
	{
		$patterns = array( // String array
			"junit.framework.TestCase",
			"junit.framework.TestResult",
			"junit.framework.TestSuite",
			"junit.framework.Assert.", // don't filter AssertionFailure
			"junit.swingui.TestRunner",
			"junit.awtui.TestRunner",
			"junit.textui.TestRunner",
			"java.lang.reflect.Method.invoke("
		);
		for ( $i = 0; $i < count( $patterns ); $i++ ) 
		{
			if ( stripos( $line, $patterns[ $i ] ) > 0 )
			{
				return true;
			}
		}
		return false;
	}

 	/*static {
 		fgMaxMessageLength= getPreference("maxmessage", fgMaxMessageLength);
 	}*/

}
?>